#include "stdafx.h"
#include "Voronoi.h"

#include "Peak.h"
#include "PeakDistance.h"
#include "../../../Framework/Toolkit.h"

#include <algorithm>
#include <vector>

using namespace Framework;

namespace MyGame {

Voronoi::Voronoi(void) {
    this->cells = 16;
    this->features = 1.0f;
    this->scale = 1.0f;
    this->type = VORONOITYPE_LINEAR;
}

Voronoi::~Voronoi(void) {
}

UINT Voronoi::GetCells() const {
    return this->cells;
}

void Voronoi::SetCells(UINT val) {
    this->cells = val;
}

FLOAT Voronoi::GetFeatures() const {
    return this->features;
}

void Voronoi::SetFeatures(FLOAT val) {
    this->features = val;
}

FLOAT Voronoi::GetScale() const {
    return this->scale;
}

void Voronoi::SetScale(FLOAT val) {
    this->scale = val;
}

VoronoiType Voronoi::GetType() const {
    return this->type;
}

void Voronoi::SetType(VoronoiType val) {
    this->type = val;
}

void Voronoi::Generate(HeightMap* heightMap) {
    int length = heightMap->GetLength();
    int width = heightMap->GetWidth();
    FLOAT** map = heightMap->GetMap();
    std::vector<Peak> voronoiSet;
    for (UINT i = 0; i < this->GetCells(); ++i) {
        UINT xCoord = rand() % width;
        UINT zCoord = rand() % length;
        FLOAT pointHeight = (FLOAT)Toolkit::RandomFloat();
        if (Toolkit::RandomFloat() > this->GetFeatures()) {
            pointHeight = 0.0f;
        }

        Peak newPeak = Peak(D3DXVECTOR2((FLOAT)xCoord, (FLOAT)zCoord), pointHeight);
        voronoiSet.push_back(newPeak);
    }

    FLOAT highestScore = 0.0f;
    for (int mz = 0; mz < length; ++mz) {
        for (int mx = 0; mx < width; ++mx) {
            std::vector<PeakDistance> peakDistances;
            for (UINT i = 0; i < this->GetCells(); ++i) {
                Peak peak = voronoiSet[i];
                D3DXVECTOR2 peakPoint = peak.Point;
                FLOAT distanceToPeak = D3DXVec2Length(&(peakPoint - D3DXVECTOR2((FLOAT)mx, (FLOAT)mz)));
                PeakDistance newPeakDistance = PeakDistance(i, distanceToPeak);
                peakDistances.push_back(newPeakDistance);
            }

            sort(peakDistances.begin(), peakDistances.end(), PeakDistance::Compare);
            PeakDistance peakDistOne = peakDistances[0];
            PeakDistance peakDistTwo = peakDistances[1];
            UINT p1 = peakDistOne.Id;
            FLOAT d1 = peakDistOne.Distance;
            FLOAT d2 = peakDistTwo.Distance;
            FLOAT scale = fabs(d1 - d2) / ((width + length) / sqrtf((FLOAT)this->GetCells()));
            Peak peakOne = voronoiSet[p1];
            FLOAT h1 = peakOne.Heigt;
            FLOAT hScore = h1 - fabs(d1 / d2) * h1;
            FLOAT asRadians;
            switch (this->type) {
            case VORONOITYPE_LINEAR:
                break;
            case VORONOITYPE_SINE:
                asRadians = hScore * D3DX_PI - D3DX_PI / 2;
                hScore = 0.5f + sin(asRadians) / 2;
                break;
            case VORONOITYPE_TANGENT:
                asRadians = hScore * D3DX_PI / 2;
                hScore = 0.5f + tan(asRadians) / 2;
                break;
            }

            hScore = (hScore * scale * this->GetScale()) + (hScore * (1.0f - this->GetScale()));
            if (hScore < 0.0f) {
                hScore = 0.0f;
            } else if (hScore > 1.0f) {
                hScore = 1.0f;
            }

            map[mx][mz] = hScore;
            if (hScore > highestScore) {
                highestScore = hScore;
            }
        }
    }

    for (int mz = 0; mz < length; ++mz) {
        for (int mx = 0; mx < width; ++mx) {
            if (highestScore == 0) {
                map[mx][mz] = 0;
            } else {
                FLOAT normalisedHeight = map[mx][mz] * (1.0f / highestScore * this->GetScale());
                map[mx][mz] = normalisedHeight;
            }
        }
    }
}

}
